﻿using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;

namespace _4_NovaAdmin.Web.Core.IM
{
    public class ImServer
    {
        protected string _server { get; set; }
        const int BufferSize = 4096;
        ConcurrentDictionary<Guid, ConcurrentDictionary<Guid, ImServerClient>> _clients = new ConcurrentDictionary<Guid, ConcurrentDictionary<Guid, ImServerClient>>();

        private readonly ImUtility _imUtility;
        public readonly StackExchange.Redis.IDatabase _redis;
        public ImServer(ImUtility imUtility)
        {
            _imUtility = imUtility;
            _redis = imUtility._redis;
            _server = imUtility.imOptions.Server;
            // 订阅频道
            imUtility._subscriber.Subscribe($"{imUtility._redisPrefix}Server{_server}", RedisSubScribleMessage);
        }

        public async Task Acceptor(HttpContext context, Func<Task> next)
        {
            // 1. WebSocket 请求检查
            if (!context.WebSockets.IsWebSocketRequest) return;

            // 2. Token 验证
            string token = context.Request.Query["token"];
            if (string.IsNullOrEmpty(token)) return;

            string tokenValue = _redis.StringGet($"{_imUtility._redisPrefix}Token{token}").ToString();
            if (string.IsNullOrEmpty(tokenValue))
                throw new Exception("授权错误：用户需通过 ImHelper.PrevConnectServer 获得包含 token 的连接");

            // 3. 解析 Token 数据  clientId: 客户端唯一标识符  clientMetaData: 客户端元数据（具体内容取决于实现）
            var (clientId, clientMetaData) = JsonConvert.DeserializeObject<(Guid, string)>(tokenValue.ToString());

            // 4. WebSocket 连接建立
            var socket = await context.WebSockets.AcceptWebSocketAsync();
            var cli = new ImServerClient(socket, clientId);
            var newid = Guid.NewGuid();

            var wslist = _clients.GetOrAdd(clientId, cliid => new ConcurrentDictionary<Guid, ImServerClient>());
            wslist.TryAdd(newid, cli);

            // 5. 在线状态更新
            _redis.HashIncrement($"{_imUtility._redisPrefix}Online", clientId.ToString());
            _redis.Publish($"evt_{_imUtility._redisPrefix}Online", tokenValue);

            // 6. 数据接收循环
            var buffer = new byte[BufferSize];
            var seg = new ArraySegment<byte>(buffer);
            try
            {
                while (socket.State == WebSocketState.Open && _clients.ContainsKey(clientId))
                {
                    var incoming = await socket.ReceiveAsync(seg, CancellationToken.None);
                    var outgoing = new ArraySegment<byte>(buffer, 0, incoming.Count);
                    // 处理接收到的消息
                }
                socket.Abort();
            }
            catch
            {
            }

            // 7. 连接清理
            wslist.TryRemove(newid, out var oldcli);
            if (wslist.IsEmpty) _clients.TryRemove(clientId, out var oldwslist);

            // 8. 在线状态减少
            var script = @"
        if redis.call('HINCRBY', KEYS[1], ARGV[1], '-1') <= 0 then
            redis.call('HDEL', KEYS[1], ARGV[1])
        end
        return 1
    ";
            _redis.ScriptEvaluate(script, new RedisKey[] { $"{_imUtility._redisPrefix}Online" }, new RedisValue[] { clientId.ToString() });

            LeaveChan(clientId, GetChanListByClientId(clientId));
            _redis.Publish($"evt_{_imUtility._redisPrefix}Offline", tokenValue);
        }

        /// <summary>
        /// 这个方法负责将消息从 Redis 订阅通道分发到各个socket客户端连接
        /// </summary>
        /// <param name="channel"></param>
        /// <param name="message"></param>
        void RedisSubScribleMessage(RedisChannel channel, RedisValue message)
        {
            try
            {
                // 1. 消息反序列化
                var data = JsonConvert.DeserializeObject<(Guid senderClientId, Guid[] receiveClientId, string content, bool receipt)>(message);

                // 2. 日志记录
                Trace.WriteLine($"收到消息：{data.content}" + (data.receipt ? "【需回执】" : ""));

                var outgoing = new ArraySegment<byte>(Encoding.UTF8.GetBytes(data.content));
                // 3. 消息处理循环 遍历所有接收者客户端ID，逐个处理消息
                foreach (var clientId in data.receiveClientId)
                {
                    // 3.1 检查接收者是否在线
                    if (_clients.TryGetValue(clientId, out var wslist) == false)
                    {
                        // 3.1.1 处理需要回执的情况
                        if (data.senderClientId != Guid.Empty && clientId != data.senderClientId && data.receipt)
                            _imUtility.SendMessage(clientId, new[] { data.senderClientId }, new
                            {
                                data.content,
                                receipt = "用户不在线"
                            });
                        continue;
                    }

                    // 获取接收者的所有 WebSocket 连接（可能有多个设备登录）
                    ImServerClient[] sockarray = wslist.Values.ToArray();

                    // 3.2.1 避免自循环发送
                    //if (clientId == data.senderClientId && sockarray.Length <= 1) continue;

                    // 3.2.2 发送消息到所有连接 
                    foreach (var sh in sockarray)
                        sh.socket.SendAsync(outgoing, WebSocketMessageType.Text, true, CancellationToken.None);

                    // 3.2.3 处理需要回执的情况
                    if (data.senderClientId != Guid.Empty && clientId != data.senderClientId && data.receipt)
                        _imUtility.SendMessage(clientId, new[] { data.senderClientId }, new
                        {
                            data.content,
                            receipt = "发送成功"
                        });
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine($"订阅方法出错了：{ex.Message}");
            }
        }


        /// <summary>
        /// 离开群聊频道
        /// </summary>
        /// <param name="clientId">客户端id</param>
        /// <param name="chans">群聊频道名</param>
        public void LeaveChan(Guid clientId, params string[] chans)
        {
            if (chans?.Any() != true) return;

            var batch = _redis.CreateBatch();

            foreach (var chan in chans)
            {
                // 删除群聊成员关系
                batch.HashDeleteAsync($"{_imUtility._redisPrefix}Chan{chan}", clientId.ToString());
                // 删除客户端群聊关联
                batch.HashDeleteAsync($"{_imUtility._redisPrefix}Client{clientId}", chan);

                // 原子递减计数并清理空频道
                var luaScript = @"
            local count = redis.call('HINCRBY', KEYS[1], ARGV[1], -1)
            if count <= 0 then
                redis.call('HDEL', KEYS[1], ARGV[1])
            end
            return 1";

                batch.ScriptEvaluateAsync(
                    luaScript,
                    new RedisKey[] { $"{_imUtility._redisPrefix}ListChan" },
                    new RedisValue[] { chan }
                );
            }

            batch.Execute();
        }


        /// <summary>
        /// 获取用户参与的所有群聊频道
        /// </summary>
        /// <param name="clientId">客户端id</param>
        /// <returns></returns>
        public string[] GetChanListByClientId(Guid clientId)
        {
            // 获取哈希表中的所有键
            RedisValue[] keys = _redis.HashKeys($"{_imUtility._redisPrefix}Client{clientId}");

            // 将 RedisValue[] 转换为 string[]
            return keys.Select(key => key.ToString()).ToArray();
        }


        public class ImServerClient
        {
            public WebSocket socket;
            public Guid clientId;

            public ImServerClient(WebSocket socket, Guid clientId)
            {
                this.socket = socket;
                this.clientId = clientId;
            }
        }
    }
}
